import * as http from "http";
import { URL } from "url";
import { Buffer } from "buffer";
import { Trie, HandlerTable, Handler } from "./controller";
import {
    Response,
    Response404,
    Response405,
    Response500,
    Html,
} from "./rest_response";
import * as zlib from "zlib";
import { IncomingMessage } from "http";
import "reflect-metadata";
import { Interceptor, AllowCrossDomain } from "./interceptor";

type ParamInfo = {
    source?: string; // 实参来源，包括 path、query、body 以及 bodySelf
    sourceName?: string; // 实参来源的名称
    name: string; // 形参名
    type: string; // 形参类型
    optional?: boolean; // 是否为可选参数，true 表示可选，false 表示必须
};

type RequestArgs = {
    pathArgs: { [key: string]: string }; // 通过 href path 表示的参数
    queryArgs: URLSearchParams; // 通过 href query 传递的参数
    bodyArgs: any; // 通过请求主体 body 传递的参数，可能时form-data格式、也可能是 json 格式
};

const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
const ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func: Function): [string] {
    let fnSourceCode = func.toString().replace(STRIP_COMMENTS, "");
    let result = fnSourceCode
        .slice(fnSourceCode.indexOf("(") + 1, fnSourceCode.indexOf(")"))
        .match(ARGUMENT_NAMES);
    if (result === null) result = [];
    return result as [string];
}

class App {
    private host: string;
    private port: number;
    private httpServer: http.Server;
    private trie: Trie;
    private handlerObj?: object;
    private interceptors: Interceptor[] = [];
    public allowOptions: boolean = false;
    constructor(host: string, port: number) {
        this.host = host;
        this.port = port;
        this.httpServer = http.createServer();
        this.trie = new Trie();

        this.httpServer.on("request", this.onRequest.bind(this));
    }

    public addInterceptor(interceptor: Interceptor) {
        this.interceptors.push(interceptor);
    }

    async onRequest(req: IncomingMessage, res: http.ServerResponse) {
        let url: URL = new URL(req.url as string, `http://${req.headers.host}`);
        let path: string = url.pathname;

        let body = await this.receiveBody(req);

        let result;
        let node = this.trie.find(path);
        if (node == null || node.handlerTable === undefined) {
            result = new Response404({ message: `未找到${path}对应的资源` });
        } else if (
            node.handlerTable[req.method as keyof HandlerTable] == undefined
        ) {
            let allowMethods = Object.keys(node.handlerTable).join(",");
            if (req.method == "OPTIONS" && this.allowOptions) {
                result = new Response(200, "OK", {
                    Allow: allowMethods,
                    "Content-Length": "0",
                });
            } else {
                result = new Response405(
                    { message: `${path}对应的资源不支持${req.method}` },
                    { Allow: allowMethods }
                );
            }
        } else {
            try {
                let requestArgs = this.extractRequestParams(url, req, body);
                let handler = node.handlerTable[
                    req.method as keyof HandlerTable
                ] as Handler;
                let args = this.findArguments(
                    requestArgs,
                    handler.function.name
                );
                result = handler.function.call(handler.context, ...args);
                if (result instanceof Promise) {
                    result = await result;
                }
            } catch (e) {
                result = new Response500({ message: `服务内部出错:${e}` });
            }
        }

        // 返回结果
        this.sendResult(req, res, result);
    }

    private sendResult(
        req: IncomingMessage,
        res: http.ServerResponse,
        result: any
    ) {
        let response: Response;
        if (result instanceof Response) {
            response = result;
        } else if (typeof result == "string") {
            response = new Response(
                200,
                "OK",
                {
                    "Content-Type": "text/plain;charset=UTF-8",
                },
                result
            );
        } else if (result instanceof Html) {
            response = new Response(
                200,
                "OK",
                { "Content-Type": "text/html;charset=UTF-8" },
                result.toString()
            );
        } else if (result instanceof Object) {
            response = new Response(
                200,
                "OK",
                { "Content-Type": "application/json" },
                JSON.stringify(result)
            );
        } else if (result == undefined || result == null) {
            response = new Response(200, "OK");
        } else {
            response = new Response(
                200,
                "OK",
                {
                    "Content-Type": "text/plain;charset=UTF-8",
                },
                result.toString()
            );
        }

        for (let interceptor of this.interceptors) {
            interceptor.processResonse(req, response);
        }

        res.writeHead(
            response.statusCode,
            response.statusMessage,
            response.headers
        );
        if (response.body) {
            if (response.body instanceof Object) {
                res.end(JSON.stringify(response.body));
            } else {
                res.end(response.body);
            }
        } else {
            res.end();
        }
    }

    /**
     * 提取 http 请求的所有参数
     * @param url 请求的完整 url，和 req.url 不同，后者只包括 path
     * @param req 请求对象
     * @param body 请求主体
     * @returns 所有的请求参数对象，包括路径参数，query 查询，body 参数
     */
    private extractRequestParams(
        url: URL,
        req: IncomingMessage,
        body?: Buffer
    ): RequestArgs {
        url.search;
        let bodyParams = undefined;
        if (body) {
            // todo 支持 text/xml、multipart/form-data
            switch (req.headers["content-type"]) {
                case "application/json":
                    bodyParams = JSON.parse(body.toString());
                    break;
                case "application/x-www-form-urlencoded":
                    bodyParams = this.parseX3wFormUrlencoded(body.toString());
                    break;

                default:
                    bodyParams = body;
                    break;
            }
        }

        return {
            pathArgs: this.trie.popPathParams(),
            queryArgs: url.searchParams,
            bodyArgs: bodyParams,
        };
    }

    /**
     * 解析 application/x-www-form-urlencoded 方式提交的数据
     * @param data application/x-www-form-urlencoded 格式的表单数据
     * @returns 包括表单数据的对象
     */
    private parseX3wFormUrlencoded(data: string): Object {
        data = decodeURI(data);
        let result: { [key: string]: any } = {};
        for (let kv of data.split("&")) {
            let [k, v] = kv.split("=", 2);
            if (result[k] == undefined) {
                result[k] = v;
            } else if (Array.isArray(result[k])) {
                result[k].push(v);
            } else {
                result[k] = [result[k], v];
            }
        }
        return result;
    }

    /**
     * 从请求参数提取出方法需要的实参
     * @param requestArgs 请求参数
     * @param functionName 适用的方法名
     * @returns 方法需要的实参列表
     */
    private findArguments(
        requestArgs: RequestArgs,
        functionName: string
    ): any[] {
        // 根据方法的形参提取实参
        let paramInfos = Reflect.getMetadata(
            "handler:paramsInfo",
            Object.getPrototypeOf(this.handlerObj),
            functionName
        ) as [ParamInfo];

        let args = [];
        for (let info of paramInfos) {
            let arg;
            switch (info.source) {
                case "path":
                    arg = requestArgs.pathArgs[info.sourceName || info.name];
                    break;
                case "query":
                    if (info.type == "Array") {
                        arg = requestArgs.queryArgs.getAll(
                            info.sourceName || info.name
                        );
                    } else {
                        arg = requestArgs.queryArgs.get(
                            info.sourceName || info.name
                        );
                    }
                    break;
                case "body":
                    arg = requestArgs.bodyArgs[info.sourceName || info.name];
                    break;
                case "bodySelf":
                    arg = requestArgs.bodyArgs;
                    break;
                default:
                    // 没有指定 source，则根据 sourceName 或 name 在所有请求参数中从查找
                    if (info.type == "Array") {
                        arg = requestArgs.queryArgs.getAll(
                            info.sourceName || info.name
                        );
                        if (
                            arg == undefined &&
                            requestArgs.bodyArgs instanceof Object
                        ) {
                            arg =
                                requestArgs.bodyArgs[
                                    info.sourceName || info.name
                                ];
                        }
                    } else {
                        arg =
                            requestArgs.pathArgs[
                                info.sourceName || info.name
                            ] ||
                            requestArgs.queryArgs.get(
                                info.sourceName || info.name
                            );
                        if (
                            arg == undefined &&
                            requestArgs.bodyArgs instanceof Object
                        ) {
                            arg =
                                requestArgs.bodyArgs[
                                    info.sourceName || info.name
                                ];
                        }
                    }
                    break;
            }

            // 将参数转成合适的类型
            if (arg == null) {
                arg = undefined;
            } else if (arg != undefined) {
                if (info.type == "Number" && typeof arg != "number") {
                    arg = Number(arg);
                } else if (info.type == "String" && typeof arg != "string") {
                    arg = String(arg);
                }
            }

            args.push(arg);
        }
        return args;
    }

    mapping(path: string, method: keyof HandlerTable) {
        return (
            target: any,
            propertyKey: string,
            descriptor: PropertyDescriptor
        ) => {
            // 提取形参名
            this.extractArgNames(target, propertyKey, descriptor);
            // 创建控制器对象
            if (this.handlerObj === undefined) {
                this.handlerObj = new target.constructor();
            }

            let handler = Object.create(null);
            handler["function"] = descriptor.value;
            handler["context"] = this.handlerObj as object;
            this.trie.insertNode(path, method, handler);
        };
    }

    get(path: string) {
        return this.mapping(path, "GET");
    }

    post(path: string) {
        return this.mapping(path, "POST");
    }

    put(path: string) {
        return this.mapping(path, "PUT");
    }

    delete(path: string) {
        return this.mapping(path, "DELETE");
    }
    // 从方法源码中提取出各个行参名，并保存到对应方法的元数据中
    private extractArgNames(
        target: any,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        if (!Reflect.hasMetadata("handler:paramsInfo", target, propertyKey)) {
            initHanlerParamsInfo(target, propertyKey);
        }

        let infos: ParamInfo[] = Reflect.getMetadata(
            "handler:paramsInfo",
            target,
            propertyKey
        );

        getParamNames(descriptor.value).forEach((name, i) => {
            infos[i].name = name;
        });
    }

    listen(backlog?: number, listeningListener?: () => void) {
        this.httpServer.listen(
            this.port,
            this.host,
            backlog,
            listeningListener
        );
    }

    // todo:处理传输编码和内容编码
    private async receiveBody(req: IncomingMessage): Promise<Buffer> {
        return new Promise((resolve, reject) => {
            let chunks: Buffer[] = [];
            req.on("data", (chunk) => {
                chunks.push(chunk);
            });

            req.on("end", () => {
                resolve(Buffer.concat(chunks));
            });
        });
    }
}

function param(source: string, sourceName?: string) {
    return (target: Object, propertyKey: string, parameterIndex: number) => {
        if (!Reflect.hasMetadata("handler:paramsInfo", target, propertyKey)) {
            initHanlerParamsInfo(target, propertyKey);
        }

        let infos = Reflect.getMetadata(
            "handler:paramsInfo",
            target,
            propertyKey
        ) as [ParamInfo];

        let info = infos[parameterIndex];
        info.source = source;
        if (sourceName != undefined) {
            info.sourceName = sourceName;
        }
    };
}

function PathParam(sourceName?: string) {
    return param("path", sourceName);
}

function QueryParam(sourceName?: string) {
    return param("query", sourceName);
}

let BodySelf = param("bodySelf");

function optional(target: Object, propertyKey: string, parameterIndex: number) {
    if (!Reflect.hasMetadata("handler:paramsInfo", target, propertyKey)) {
        initHanlerParamsInfo(target, propertyKey);
    }

    let infos = Reflect.getMetadata(
        "handler:paramsInfo",
        target,
        propertyKey
    ) as [ParamInfo];

    infos[parameterIndex].optional = true;
}

function initHanlerParamsInfo(target: any, propertyKey: string) {
    if (Reflect.hasMetadata("handler:paramsInfo", target, propertyKey)) {
        return;
    }

    let types: Function[] = Reflect.getMetadata(
        "design:paramtypes",
        target,
        propertyKey
    );
    let paramsInfo = types.map((t) => {
        return { type: t.name };
    });
    Reflect.defineMetadata(
        "handler:paramsInfo",
        paramsInfo,
        target,
        propertyKey
    );
}

class Request {
    method: string;
    url: URL;
    httpVersion: string;
    headers: Map<string, string>;
    private rawBody: Buffer;
    private _body?: Buffer;
    private _text?: string;
    private _json?: object;

    constructor(
        method: string,
        url: string | URL,
        httpVersion: string,
        headers?: Map<string, string>,
        rawBody?: Buffer
    ) {
        this.method = method;
        if (typeof url == "string") {
            this.url = new URL(url);
        } else {
            this.url = url;
        }
        this.httpVersion = httpVersion;
        this.headers = headers || new Map();
        this.rawBody = rawBody || Buffer.alloc(0);
    }

    get body(): Buffer {
        if (this._body === undefined) {
            switch (this.headers.get("content-encoding")) {
                case "gzip":
                    this._body = zlib.gunzipSync(this.rawBody);
                    break;
                case "deflate":
                    this._body = zlib.inflateSync(this.rawBody);
                    break;
                case "br":
                    this._body = zlib.brotliDecompressSync(this.rawBody);
                    break;
                case "compress":
                    throw new Error("不支持 compress 压缩格式");
                case undefined:
                    this._body = this.rawBody;
                    break;
                default:
                    throw new Error(
                        `不支持的内容编码 ${this.headers.get(
                            "content-encoding"
                        )}`
                    );
            }
        }

        return this._body;
    }

    get text(): string {
        if (this._text == undefined) {
            this._text = this.body.toString();
        }

        return this._text;
    }

    get json(): Object {
        if (this._json == undefined) {
            let r = JSON.parse(this.text);
            if (r instanceof Object) {
                this._json = r as Object;
            } else {
                throw new Error("body不能转成json对象");
            }
        }

        return this._json;
    }
}

export { App, Request, PathParam, QueryParam, BodySelf, optional };
